package yiyuan.code.filedemo.file.service;


import com.sun.istack.internal.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import yiyuan.code.filedemo.file.FileOperatorContext;
import yiyuan.code.filedemo.file.domain.definemodel.basicstore.Etfileinfo;
import yiyuan.code.filedemo.file.domain.moren.repository.basicstore.EtfileinfoInter;
import yiyuan.code.filedemo.file.dto.ObjectResponse;
import yiyuan.code.filedemo.file.dto.UploadRequestParam;
import yiyuan.code.filedemo.file.enums.Enums;
import yiyuan.code.filedemo.file.enums.FileEnum;
import yiyuan.code.filedemo.file.enums.FileOperatorTypeEnum;
import yiyuan.code.filedemo.file.exception.CustomUnifiedException;
import yiyuan.code.filedemo.file.operator.ExternalFileOperator;
import yiyuan.code.filedemo.file.operator.FileOperatorInter;
import yiyuan.code.filedemo.utils.FileUtils;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 文件通用服务
 *
 * @author CPYF-YI MAO
 * @date 2021/5/26 15:21
 */
@Slf4j
@Service
public class FileCommonService {

  @Resource
  private FileOperatorContext fileOperatorContext;
  @Resource
  private EtfileinfoInter fileinfoInter;


  /**
   * 上传为临时存储文件
   *
   * @param file 文件
   * @return 文件ID
   */
    public String uploadAsTemp(MultipartFile file) {
    /*String mimeType = FileUtils.detectMimeType(file.getInputStream());file.getContentType();
    if (!checkMimeType(mimeType)) {
      throw CustomUnifiedException.msg("文件上传-非法文件格式");
    }*/
    String name = file.getOriginalFilename();
    if (StringUtils.isBlank(name)) {
      throw CustomUnifiedException.aom("文件上传-获取文件名称为空");
    }

    //剔除文件分隔符 windows
    if (name.contains("\\")) {
      name = name.substring(name.lastIndexOf('\\') + 1);
    }
    //linux
    if (name.contains("/")) {
      name = name.substring(name.lastIndexOf('/') + 1);
    }
    if (name.length() > 128) {
      throw CustomUnifiedException.aom("文件名超过限制长度128位");
    }

    String mimeType = "";
    try (InputStream is = file.getInputStream()) {
      //文件有效的类型检测
      mimeType = FileUtils.detectMimeType(is, name);
      if (!checkMimeType(mimeType)) {
        throw CustomUnifiedException.msg("文件上传-非法文件格式");
      }
    } catch (Exception e) {
      throw CustomUnifiedException.msg("上传文件流读取异常", e);
    }

    String suffix = FilenameUtils.getExtension(name);
    String key = UUID.randomUUID().toString();
    //获取激活当前的文件操作器
    FileOperatorInter operatorInter = fileOperatorContext.active();
    FileOperatorTypeEnum typeEnum = FileOperatorTypeEnum.getIgnoreCaseByName(operatorInter.getClass());
    FileEnum.Type fileTypeEnum = FileEnum.Type.getTypeEnum(typeEnum.name());
    //获取到所有参数 封装到UploadRequestParam
    UploadRequestParam requestParam = UploadRequestParam.builder()
      .key(key).contentType(mimeType).size(file.getSize()).extName(suffix)
      .build();
    ObjectResponse objectResponse;
    try {
      objectResponse = operatorInter.uploadFile(requestParam, file.getInputStream());
    } catch (IOException e) {
      throw CustomUnifiedException.aom("文件上传-获取文件流异常", e);
    }

    Etfileinfo fileinfo = new Etfileinfo();
    fileinfo.setFid(key);
    fileinfo.setFname(name);
    fileinfo.setFstorename(name);
    if ("".equals(suffix) || suffix==null) {
      fileinfo.setFsuffix(suffix);
    } else {
      fileinfo.setFsuffix('.' + suffix);
    }
    fileinfo.setFstoreplace(objectResponse.getStorePath());
    fileinfo.setFtype(fileTypeEnum.getValue());
    fileinfo.setFstate(FileEnum.State.TEMPORARY.getValue());
    fileinfo.setFtime(Timestamp.valueOf(LocalDateTime.now()));
    fileinfoInter.save(fileinfo);
    return key;
  }

  /**
   * 流式下载文件
   *
   * @param fileinfo 文件信息
   * @param consumer 消费者
   */
  public void downloadAsStream(Etfileinfo fileinfo, Consumer<InputStream> consumer) {

    Objects.requireNonNull(fileinfo, "文件下载-必要参数[Fileinfo]不可为空");
    Objects.requireNonNull(consumer, "文件下载-必要参数[Consumer]不可为空");
    FileOperatorInter operatorInter = getOperatorByType(fileinfo);

      operatorInter.getFileAsStream(fileinfo.getFstoreplace(), consumer);

  }
  /**
   * 下载文件并输出到响应流
   *
   * @param id       文件ID
   * @param request  请求
   * @param response 响应
   */
  public void downloadToResponse(String id, HttpServletRequest request, HttpServletResponse response) {
    Etfileinfo fileInfo = getFileInfoById(id);
    downloadAsStream(fileInfo, (InputStream is) -> {
      //设置相应类型application/octet-stream （注：application/octet-stream 为通用，一些其它的类型苹果浏览器下载内容可能为空）
      response.reset();
      response.addHeader("Access-Control-Allow-Origin", "*");
      response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
      response.addHeader("Access-Control-Allow-Headers", "Content-Type");
      String userAgent = request.getHeader("user-agent").toLowerCase();
      String fileName = fileInfo.getFname();
      if (userAgent.contains("msie") || userAgent.contains("like gecko")) {
        // win10 ie edge 浏览器 和其他系统的ie
        try {
          fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
          throw CustomUnifiedException.aom("URLEncoder编码文件名称异常", e);
        }
      } else {
        fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
      }
      response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + '"');
      response.setContentType("application/octet-stream");

      try (ServletOutputStream os = response.getOutputStream()) {
        IOUtils.copy(is, os);
      } catch (IOException e) {
        throw CustomUnifiedException.aom("文件下载-获取servlet响应流异常", e);
      }
    });
  }

  /**
   * 下载文件并输出到响应流
   *
   * @param fname    文件名称
   * @param request  请求
   * @param response 响应
   */
  public void downloadToResponse(String fname, HttpServletRequest request, HttpServletResponse response,
                                 Supplier<InputStream> inputStreamSupplier) {
    //设置相应类型application/octet-stream （注：application/octet-stream 为通用，一些其它的类型苹果浏览器下载内容可能为空）
    String fileName = fname;
    response.reset();
    response.addHeader("Access-Control-Allow-Origin", "*");
    response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
    response.addHeader("Access-Control-Allow-Headers", "Content-Type");
    String userAgent = request.getHeader("user-agent").toLowerCase();
    if (userAgent.contains("msie") || userAgent.contains("like gecko")) {
      // win10 ie edge 浏览器 和其他系统的ie
      try {
        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
      } catch (UnsupportedEncodingException e) {
        throw CustomUnifiedException.aom("URLEncoder编码文件名称异常", e);
      }
    } else {
      fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
    }
    response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + '"');
    response.setContentType("application/octet-stream");

    try (ServletOutputStream os = response.getOutputStream();
         InputStream is = inputStreamSupplier.get()) {
      IOUtils.copy(is, os);
    } catch (IOException e) {
      throw CustomUnifiedException.aom("文件下载-获取servlet响应流异常", e);
    }
  }

  /**
   * 删除文件
   *
   * @param fileId 文件Id
   */
  public boolean deleteFile(String fileId) {
    Etfileinfo fileinfo = fileinfoInter.getById(fileId);
    if (fileinfo == null) {
      return false;
    }
    FileOperatorInter operatorInter = this.getOperatorByType(fileinfo);
    operatorInter.deleteFile(fileId);
    return true;
  }


  /**
   * 根据文件信息获取对应文件操作器
   *
   * @param fileinfo 文件信息
   * @return 文件操作器
   */
  private FileOperatorInter getOperatorByType(Etfileinfo fileinfo) {
    String id = fileinfo.getFid();
    //确认目标文件存储类型
    FileEnum.Type type = Enums.getOptionalByValue(FileEnum.Type.class, fileinfo.getFtype())
      .orElseThrow(() -> CustomUnifiedException.aom(String.format("文件下载-目标文件[%s]存储类型不存在", id)));
    //匹配文件类型操作器
    FileOperatorTypeEnum operatorTypeEnum = FileOperatorTypeEnum.getIgnoreCaseByName(type.name());
    //获取指定的文件操作器
    return fileOperatorContext.getOperatorByType(operatorTypeEnum);
  }

  /**
   * 根据ID查询文件信息
   *
   * @param id 文件ID
   * @return Etfileinfo
   * @throws CustomUnifiedException 为空时抛出
   */
  private Etfileinfo getFileInfoById(String id) {
    return fileinfoInter.findById(id).orElseThrow(() -> CustomUnifiedException.msg("目标文件不存在"));
  }

  /**
   * 检查允许的文件MimeType
   *
   * @param mimeType 媒体格式
   * @return 是否允许
   */
  private boolean checkMimeType(@NotNull String mimeType) {
    Objects.requireNonNull(mimeType);
    //允许的文件格式
    List<String> allowedDoc = Arrays.asList(
      //pdf
      "application/pdf",
      //word
      "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      //excel
      "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      //压缩文件  rar格式：application/x-rar-compressed; version=5
      "application/zip", "application/x-7z-compressed", "application/x-rar-compressed", "application/x-zip-compressed",
      //图片
      "image/",
      //视频
      "video/",
      //投标的签名格式
      "application/pkcs7-signature");
    for (String doc : allowedDoc) {
      if (mimeType.startsWith(doc)) {
        return true;
      }
    }
    return false;
  }


}
